// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jingyuan
#pragma once

#include <algorithm>
#include <cstdint>
#include <memory>

#include <gflags/gflags.h>

#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/strategy.h"

namespace coin2::appcoin2::support::account_control {

using coin2::app::Driver;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::IOrderSubsystem;
using coin2::exchange::base::order::OrderUpdate;
using coin2::strategy::PassiveExecutorConfig;
using coin2::strategy::order_executor::PassiveOrderExecutor;
using coin2::strategy::util::StrategyReporter2;
using IProduct = coin2::exchange::base::symbology::IProduct;
using IProductHolder = coin2::exchange::base::symbology::IProductHolder;
using OrderDirection = coin::proto::OrderDirection;

constexpr double EPSILON = 1e-6;

struct PaceControl {
  PaceControl(
      double lot_size,
      double round_duration_minute,
      bool round_hit_taker,
      double taker_cooldown_second,
      double taker_payup_bp)
      : lot_size0(lot_size),
        round_duration_minute(round_duration_minute),
        round_hit_taker(round_hit_taker),
        taker_cooldown_second(taker_cooldown_second),
        taker_payup_bp(taker_payup_bp) {}

  void UpdateFill(int64_t timestamp, double fill_qty);

  double GetRemainingPace(int64_t time_elapsed, double current_fill_qty) const;

  double GetAggLotSize(int64_t time_elapsed, double current_fill_qty) const;

  void ExecuteOrder(
      int64_t timestamp,
      PassiveOrderExecutor* executor,
      double ask0,
      double bid0,
      double sell_price,
      double buy_price,
      double pass_bp,
      bool log_order,
      double initial_position,
      double target_position);

  const double lot_size0;
  const double round_duration_minute;
  const bool round_hit_taker;
  const double taker_cooldown_second;
  const double taker_payup_bp;

  int64_t timestamp_begin = 0L;
  int64_t timestamp_last_order = 0L;
  double cum_fill_qty = 0;
};

class ApproachTargetPositionStrategy : public coin2::strategy::IStrategy {
 public:
  ApproachTargetPositionStrategy(
      double pass_bp,
      double target_position,
      bool use_target_position_as_delta,
      double lot_size,
      double leverage,
      bool hard_close,
      bool post_at_mid,
      double order_update_period,
      double max_posting_period,
      double sticky_bp,
      double tick_qty,
      double min_price,
      double max_price,
      double auto_shutdown_sec,
      double fill_cooldown_window,
      double round_duration_minute,
      bool round_hit_taker,
      double taker_cooldown_second,
      double taker_payup_bp,
      double market_share_limit_window_period,
      double market_share_limit,
      double limit_payup_bp_to_ma1h,
      bool ignore_self_order,
      std::string rounding_policy_str)
      : hard_close_(hard_close),
        post_at_mid_(post_at_mid),
        lot_size_(lot_size),
        pass_bp_(pass_bp),
        target_position_(target_position),
        use_target_position_as_delta_(use_target_position_as_delta),
        leverage_(leverage),
        sticky_bp_(sticky_bp),
        shutdown_ts_(auto_shutdown_sec * 1e9),
        fill_cooldown_window_(fill_cooldown_window * 1e9),
        tick_qty_(tick_qty),
        min_price_(min_price),
        max_price_(max_price),
        order_update_period_(order_update_period * 1e9),
        max_posting_period_(max_posting_period * 1e9),
        pace_control_(
            lot_size,
            round_duration_minute,
            round_hit_taker,
            taker_cooldown_second,
            taker_payup_bp),
        market_share_limit_window_period_(market_share_limit_window_period * 1e9),
        market_share_limit_(market_share_limit),
        limit_payup_bp_to_ma1h_(limit_payup_bp_to_ma1h),
        ignore_self_order_(ignore_self_order),
        rounding_policy_str_(rounding_policy_str) {
    CHECK(!hard_close || std::fabs(target_position) < EPSILON) << "hard close must have 0 position";
    CHECK(lot_size > 0) << "lot_size must be greater than zero";
    CHECK(!(hard_close && use_target_position_as_delta))
        << "cannnot specify delta position and hard close";
    CHECK(min_price_ <= 0 || max_price_ <= 0 || max_price <= min_price)
        << "max_price(max buy price) must be SMALLER than min sell price";
    CHECK_LE(limit_payup_bp_to_ma1h_, 10000);
  }

  void Init(Driver* driver) override;
  void onCleanup(Driver* driver) override;

  void onBookFeed(const FeedUpdate& upd) override;
  void onTradeFeed(const FeedUpdate& upd) override;
  void onAccountOrder(const OrderUpdate& upd) override;
  void onAccountInfo(const OrderUpdate& upd) override;
  void onOrderLog(const OrderGatewayLog& log) override;

  void CheckDone(int64_t timestamp);
  void UpdateMinMaxPosition(int64_t timestamp);
  void LogPosition();

 private:
  void InitAuthKey(::coin2::app::Driver* driver);

  IOrderSubsystem* os();
  bool isQtyClose(double a, double b);

  Driver* driver_{nullptr};
  std::unique_ptr<PassiveOrderExecutor> executor_;

  std::string product_key_;
  const IProductHolder* product_holder_;
  PassiveExecutorConfig exe_config_;
  bool hard_close_;
  bool post_at_mid_;
  double lot_size_;
  double pass_bp_;
  double target_position_;
  bool use_target_position_as_delta_;
  double leverage_;
  double sticky_bp_;
  double shutdown_ts_;
  double fill_cooldown_window_;
  double filled_notional_{0};
  double filled_qty_{0};
  std::optional<double> initial_position_{std::nullopt};
  double tick_qty_;

  double min_price_{0};
  double max_price_{0};

  int64_t order_update_period_;
  int64_t max_posting_period_;
  int64_t last_report_ts_{0};
  int64_t last_position_ts_{0};
  PaceControl pace_control_;
  double market_share_limit_window_period_;
  double market_share_limit_;

  typedef presto::math::TimeWindowMovingAverage<double> twmad;
  double limit_payup_bp_to_ma1h_;
  twmad midp_1h_ = twmad(3'600'000'000'000L);
  bool ignore_self_order_{true};
  std::string rounding_policy_str_;

  int64_t first_feed_ts_{0};
  std::unique_ptr<AuthKey> auth_key_;
  std::unique_ptr<StrategyReporter2> strat_reporter_;
};
}  // namespace coin2::appcoin2::support::account_control
